package pkg.genericity.DAO;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import pkg.genericity.annot.Parser;
import pkg.genericity.utils.BeanUtil;
import pkg.genericity.utils.Utils;

/**
 * 
 * @author Farrukh Shakil
 *
 */
public class BeanDAO extends JDBCDAO {
	
	private BeanUtil beanutil;
	
	public BeanDAO() {
		beanutil= new BeanUtil();
	}
	
	
	public Boolean addBean(Object object) {
		
		return executeUpdate(buildInsertStatement(object)) > 0;
	}

	
	/**
	 * Retrieves all beans from DB for a given class
	 * @param clazz
	 * @return
	 */
	public List<Object> getBeans(Class<?> clazz)
	{
		List<Object[]> list =getRecords(clazz, true);
		List<Object> objlist = new ArrayList<Object>();
		Object[] fieldnames =list.remove(0);
		for(Object[] record:list) {
			try {
				Object obj = clazz.newInstance();
				for (int colNo=0;colNo < record.length; colNo++) {
					obj = beanutil.updateField(obj, fieldnames[colNo].toString(), record[colNo]);
				}
				objlist.add(obj);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return objlist;
	}
	
	
	/**
	 * Get All records from DB for a given class
	 * @param clazz
	 * @param includeHeaders
	 * @return
	 */
	public List<Object[]> getRecords(Class<?> clazz, Boolean includeHeaders)
	{
		List<Object[]> list= executeQuery(buildSelectStatement(clazz),true);
		String[] fieldnames = getMatchingFieldNames(clazz, list.remove(0));
		list.add(0,fieldnames);
		return list;
	}
	
	
	/**
	 * Basically converts DataBase headers (in format ABC_XYZ) to class field format(abcXyz)
	 * @param clazz
	 * @param headers
	 * @return
	 */
	public String[] getMatchingFieldNames(Class<?> clazz, Object[] headers) {
		String []matches = new String[headers.length];
		for(Field field:clazz.getDeclaredFields()) {
			for(int colNo=0;colNo < headers.length;colNo++) {
				if(headers[colNo].toString().replaceAll("_", "").toLowerCase().equals(field.getName().toLowerCase())) {
					matches[colNo]=field.getName();
					break;
				}
			}
		}
		return matches;
	}
	
	
	/**
	 * Builds a Select * statement
	 * @param clazz
	 * @return
	 */
	public String buildSelectStatement(Class<?> clazz)
	{
		return buildSelectStatement(clazz.getSimpleName());
	}

	
	/**
	 * Builds an insert statement to insert given object
	 * @param object
	 * @return
	 */
	public String buildInsertStatement(Object object)
	{
		Field []fields = object.getClass().getDeclaredFields();
		String queryFieldNamesPart="";
		String queryValuesPart="";
		for (Field field:fields) {
			queryFieldNamesPart+=toDBFieldName(field.getName())+",";
			queryValuesPart+= prepareSingleValue(object, object.getClass().getSimpleName(), field);
		}
		queryFieldNamesPart=queryFieldNamesPart.substring(0, queryFieldNamesPart.lastIndexOf(","));
		queryValuesPart=queryValuesPart.substring(0,queryValuesPart.lastIndexOf(","));
		return prepareInsertQuery(object.getClass().getSimpleName(),queryFieldNamesPart, queryValuesPart);
	}

	
	/**
	 * Helper method buildInsertStatement Method. Prepares a single value for value part of query. Able to handle null etc
	 * @param object
	 * @param tablename
	 * @param field
	 * @return
	 */
	private String prepareSingleValue(Object object, String tablename, 	Field field) {
		String wraparound=(field.getType().equals(String.class)? "'":"");
		return wraparound+ ( new Parser().isAutoIncrement(field) ?
									getMaxId(tablename)+1: 
									removeNull(beanutil.getFieldValue(object, field.getName()),field.getType())  ) + wraparound+",";
	}

	
	private Object removeNull(Object fieldvalue, Class<?> fieldType) {
		return (fieldType.equals(String.class) && fieldvalue==null) ? "":fieldvalue;
	}

	
	/**
	 * Converts conventional fieldnames(abcDef) to conventional columannames (abc_def)
	 * @param beanFieldName
	 * @return
	 */
	private String toDBFieldName(String beanFieldName)
	{
		return beanFieldName.replaceAll("([A-Z])", "_$1").trim().toLowerCase();
	}
	
	
	/**
	 * compares the given class fields with the database fields based on their names
	 * @param clazz
	 */
	public void verifyAndUpdateDB(Class<?> clazz)
	{
		this.createTable(clazz);
		String[] fields = beanutil.getBeanFieldNames(clazz);
		List<String> exclusions = Utils.getExclusions();
		if (exclusions.contains("serialVersionUID")) {
			exclusions.remove(exclusions.indexOf("serialVersionUID"));
		}
		String[] fieldsReformatted = new String[fields.length+exclusions.size()];
		String[] columns = getColumnNames(clazz.getSimpleName());
		int i=0;
		for (String str:fields) { 
				fieldsReformatted[i++] = toDBFieldName(str);
			}
		for (String str:exclusions) {
				fieldsReformatted[i++] = str;
		}
		String []addCols = new Utils().compareArrays(fieldsReformatted, columns);
		String []remCols = new Utils().compareArrays(columns, fieldsReformatted);
		alterCols(clazz.getSimpleName(), true, addCols);
		alterCols(clazz.getSimpleName(), false, remCols);
	}
	
	/**
	 * creates a new database table
	 * @param clazz
	 * @return
	 */
	public boolean createTable(Class<?> clazz)
	{
		if (! isTableExist(clazz.getSimpleName())) {
		   return createTable(clazz.getSimpleName(), beanutil.getBeanFieldNames(clazz) ,null);
		}
		 return false;  
	}
	
}
